#include "window.h"

///////////////////////
/* Globalni promenne */
#ifndef _WIN32
/* POSIX */
static bool ctxErrorOccurred = false;     //< chyba kontextu

/* Procedura, ktera se stara o chyby v kontextu */
static int ctxErrorHandler(Display *mDisplay, XErrorEvent *mErrorEvent)
{
	mDisplay = mDisplay;
	mErrorEvent = mErrorEvent;
  ctxErrorOccurred = true;
  return 0;
}
#endif
///////////////////////

/////////////////////
/* Definice funkci */

/* Pouze Windows - callback pri prijeti zpravy */
#ifdef _WIN32
LRESULT CALLBACK winMessageCallback(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
  switch(uMsg)
  {
		case WM_ACTIVATE:						
		  return 0;

    case WM_SYSCOMMAND:
      switch(wParam)
      {
				case SC_SCREENSAVE:
				case SC_MONITORPOWER:
          return 0;
      }
      break;

    // Ukonceni okna
    case WM_CLOSE:
      PostQuitMessage(0);
      break;

    // Stisknuti klavesy
    case WM_KEYDOWN:
      g_mInput.mKeys[wParam] = 1;
      break;

    // Uvolneni klavesy
    case WM_KEYUP:
      g_mInput.mKeys[wParam] = 0;
      break;

    // Posun mysi
		case WM_MOUSEMOVE:
      g_mInput.mMousePos[0] = (int)LOWORD(lParam);
      g_mInput.mMousePos[1] = (int)HIWORD(lParam);
			break;

    // Stisk leveho tlacitka mysi
		case WM_LBUTTONDOWN:
      g_mInput.mMouseLeft = true;
			break;

    // Uvolneni leveho tlacitka mysi
		case WM_LBUTTONUP:
      g_mInput.mMouseLeft = false;
			break;
      
    // Stisk praveho tlacitka mysi
		case WM_RBUTTONDOWN:
      g_mInput.mMouseRight = true;
			break;
      
    // Uvolneni praveho tlacitka mysi
		case WM_RBUTTONUP:
      g_mInput.mMouseRight = false;
			break;
  }

	return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
#endif

/* Vytvor okno */
void winCreateWindow(CWindow *mWindow)
{
#ifdef _WIN32
/* Windows */
  unsigned int mOglPixelFormat;
  WNDCLASS mWindowClass;
  DWORD mWindowExtendedStyle;
  DWORD mWindowStyle;
  RECT mWindowRect;

  mWindowRect.left = (long)0;
  mWindowRect.right = (long)mWindow->mWidth;
  mWindowRect.top = (long)0;
  mWindowRect.bottom = (long)mWindow->mHeight;

  mWindow->mInstance = GetModuleHandle(NULL);
  mWindowClass.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
  mWindowClass.lpfnWndProc = (WNDPROC)winMessageCallback;
  mWindowClass.cbClsExtra = 0;
  mWindowClass.cbWndExtra = 0;
  mWindowClass.hInstance = mWindow->mInstance;
  mWindowClass.hIcon = LoadIcon(NULL, IDI_WINLOGO);
  mWindowClass.hCursor = LoadCursor(NULL, IDC_ARROW);
  mWindowClass.hbrBackground = NULL;
  mWindowClass.lpszMenuName = NULL;
  mWindowClass.lpszClassName = "SkyeCuillin";								

	if (!RegisterClass(&mWindowClass))
	{
		MessageBox(NULL, "Error: On line; File src/window/window.cpp; Error on RegisterClass", "Error", MB_OK | MB_ICONEXCLAMATION);
    exit(-1);
	}

  if(mWindow->mFullscreen)
  {
    DEVMODE mScreenSettings;

    memset(&mScreenSettings, 0, sizeof(mScreenSettings));

    mScreenSettings.dmSize = sizeof(mScreenSettings);
    mScreenSettings.dmPelsWidth = mWindow->mWidth;
    mScreenSettings.dmPelsHeight = mWindow->mHeight;
    mScreenSettings.dmBitsPerPel = mWindow->mBitsPerPixel;
    mScreenSettings.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT;

    if(ChangeDisplaySettings(&mScreenSettings, CDS_FULLSCREEN) != DISP_CHANGE_SUCCESSFUL)
    {
	    MessageBox(NULL, "Error: On line; File src/window/window.cpp; Error on ChangeDisplaySettings;\nRequested resolution is not supported by your video card.", "Error", MB_OK | MB_ICONEXCLAMATION);
      exit(-1);
    }
  }
  
  if(mWindow->mFullscreen)
  {
    mWindowExtendedStyle = WS_EX_APPWINDOW;
    mWindowStyle = WS_POPUP;
  }
  else
  {
    mWindowExtendedStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;
    mWindowStyle = WS_OVERLAPPEDWINDOW;
  }
  
	AdjustWindowRectEx(&mWindowRect, mWindowStyle, FALSE, mWindowExtendedStyle);	

  mWindow->mWindow = CreateWindowEx(mWindowExtendedStyle, "SkyeCuillin", "Gothic - Remake", 
    mWindowStyle | WS_CLIPSIBLINGS | WS_CLIPCHILDREN, 0, 0, mWindowRect.right - mWindowRect.left, 
    mWindowRect.bottom - mWindowRect.top, NULL, NULL, mWindow->mInstance, NULL);

  if(!mWindow->mWindow)
  {
	  MessageBox(NULL, "Error: On line; File src/window/window.cpp; Error on CreateWindowEx;", "Error", MB_OK | MB_ICONEXCLAMATION);
    exit(-1);
  }

  static PIXELFORMATDESCRIPTOR mPFD = 
  {
    sizeof(PIXELFORMATDESCRIPTOR),
    1,
    PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER,
    PFD_TYPE_RGBA,
    mWindow->mBitsPerPixel,
    0, 0, 0, 0, 0, 0,
    8,
    0,
    0,
    0, 0, 0, 0,
    24,
    8,
    0,
    PFD_MAIN_PLANE,
    0,
    0, 0, 0
  };
  
  
  mWindow->mDeviceContext = GetDC(mWindow->mWindow);

  if(!mWindow->mDeviceContext)
  {
	  MessageBox(NULL, "Error: On line; File src/window/window.cpp; Error on GetDC;", "Error", MB_OK | MB_ICONEXCLAMATION);
    exit(-1);
  }

  mOglPixelFormat = ChoosePixelFormat(mWindow->mDeviceContext, &mPFD);

  if(!mOglPixelFormat)
  {
	  MessageBox(NULL, "Error: On line; File src/window/window.cpp; Error on ChoosePixelFormat;", "Error", MB_OK | MB_ICONEXCLAMATION);
    exit(-1);
  }

	if(!SetPixelFormat(mWindow->mDeviceContext, mOglPixelFormat, &mPFD))	
	{
	  MessageBox(NULL, "Error: On line; File src/window/window.cpp; Error on SetPixelFormat;", "Error", MB_OK | MB_ICONEXCLAMATION);
    exit(-1);
	}

  mWindow->mOglContext = wglCreateContext(mWindow->mDeviceContext);

  if(!mWindow->mOglContext)
  {
	  MessageBox(NULL, "Error: On line; File src/window/window.cpp; Error on wglCreateContext;", "Error", MB_OK | MB_ICONEXCLAMATION);
    exit(-1);
  }

  wglMakeCurrent(mWindow->mDeviceContext, mWindow->mOglContext);

  ShowWindow(mWindow->mWindow, SW_SHOW);
  SetForegroundWindow(mWindow->mWindow);
  SetFocus(mWindow->mWindow);

  SetCursorPos(mWindow->mWidth >> 1, mWindow->mHeight >> 1);
#else
/* POSIX */
	mWindow->mDisplay = XOpenDisplay(0);
	if(!mWindow->mDisplay)
	{
		printf("Error: On line 18; File src/window/window.cpp; Error on XOpenDisplay;\n");
		exit(1);
	}

	static int32_t mOglVisualAttribs[] = 
	{
		GLX_X_RENDERABLE, True,
		GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
		GLX_RENDER_TYPE, GLX_RGBA_BIT,
		GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR,
		GLX_RED_SIZE, 8,
		GLX_GREEN_SIZE, 8,
		GLX_BLUE_SIZE, 8,
		GLX_ALPHA_SIZE, 8,
		GLX_DEPTH_SIZE, 24,
		GLX_STENCIL_SIZE, 8,
		GLX_DOUBLEBUFFER, True,
		None
	};

	int32_t mFBCount;
	GLXFBConfig *mFrameBufferConfig = glXChooseFBConfig(mWindow->mDisplay, DefaultScreen(mWindow->mDisplay), mOglVisualAttribs, &mFBCount);
	if(!mFrameBufferConfig)
	{
		printf("Error: On line 42; File src/window/window.cpp; Error on glXChooseFBConfig;\n");
		exit(1);
	}

	int mBestFrameBuffer = -1, mWorstFrameBuffer = -1, mBestSamples = -1, mWorstSamples = 999;
	for(int i = 0; i < mFBCount; i++)
	{
		XVisualInfo *mVisualInfo = glXGetVisualFromFBConfig(mWindow->mDisplay, mFrameBufferConfig[i]);

		if(mVisualInfo)
		{
			int mSampleBuffers, mSamples;
			glXGetFBConfigAttrib(mWindow->mDisplay, mFrameBufferConfig[i], GLX_SAMPLE_BUFFERS, &mSampleBuffers);
			glXGetFBConfigAttrib(mWindow->mDisplay, mFrameBufferConfig[i], GLX_SAMPLES, &mSamples);

			if((mBestFrameBuffer < 0) || ((mSampleBuffers) && (mSamples > mBestSamples)))
			{
				mBestFrameBuffer = i;
				mBestSamples = mSamples;
			}

			if((mWorstFrameBuffer < 0) || ((!(mSampleBuffers)) || (mSamples < mWorstSamples)))
			{
				mWorstFrameBuffer = i;
				mWorstSamples = mSamples;
			}
		}

		XFree(mVisualInfo);
	}

	GLXFBConfig mSelectedFrameBuffer = mFrameBufferConfig[mBestFrameBuffer];

	XFree(mFrameBufferConfig);

	XVisualInfo *mVisualInfo = glXGetVisualFromFBConfig(mWindow->mDisplay, mSelectedFrameBuffer);

	XSetWindowAttributes mWindowAttributes;
	mWindowAttributes.colormap = mWindow->mColorMap = XCreateColormap(mWindow->mDisplay, RootWindow(mWindow->mDisplay, mVisualInfo->screen), mVisualInfo->visual, AllocNone);
	mWindowAttributes.background_pixmap = None;
	mWindowAttributes.border_pixel = 0;
	mWindowAttributes.event_mask = StructureNotifyMask;

	if(mWindow->mFullscreen)
	{
		mWindowAttributes.override_redirect = True;
		mWindow->mWindow = XCreateWindow(mWindow->mDisplay, RootWindow(mWindow->mDisplay, mVisualInfo->screen), 0, 0, mWindow->mWidth, mWindow->mHeight, 0, mVisualInfo->depth, InputOutput, mVisualInfo->visual, CWBorderPixel|CWColormap|CWEventMask|CWOverrideRedirect, &mWindowAttributes);
	}
	else
	{
		mWindow->mWindow = XCreateWindow(mWindow->mDisplay, RootWindow(mWindow->mDisplay, mVisualInfo->screen), 0, 0, mWindow->mWidth, mWindow->mHeight, 0, mVisualInfo->depth, InputOutput, mVisualInfo->visual, CWBorderPixel|CWColormap|CWEventMask, &mWindowAttributes);
	}

	if (!mWindow->mWindow)
	{
		printf("Error: On line 88 - 96; File src/window/window.cpp; Error on XCreateWindow;\n");
		exit(1);
	}

	XFree(mVisualInfo);

	XStoreName(mWindow->mDisplay, mWindow->mWindow, "Otte Engine");
	XSelectInput(mWindow->mDisplay, mWindow->mWindow, ButtonPressMask|ButtonReleaseMask|StructureNotifyMask|KeyPressMask|KeyReleaseMask|KeymapStateMask|MotionNotify|PointerMotionMask|ExposureMask);

	XMapWindow(mWindow->mDisplay, mWindow->mWindow);

	mWindow->mOglContext = 0;
	ctxErrorOccurred = false;
	int32_t (*mOldHandler)(Display*, XErrorEvent*) = XSetErrorHandler(&ctxErrorHandler);
	mWindow->mOglContext = glXCreateNewContext(mWindow->mDisplay, mSelectedFrameBuffer, GLX_RGBA_TYPE, 0, true);

	XSync(mWindow->mDisplay, false);

	XSetErrorHandler(mOldHandler);

	if(ctxErrorOccurred || !mWindow->mOglContext)
	{
		printf("Error: On line 114; File src/window/window.cpp; Error on glXCreateNewContext;\n");
		exit(1);
	}

	if (!glXIsDirect(mWindow->mDisplay, mWindow->mOglContext))
	{
		printf("Indirect GLX rendering context obtained\n");
	}
	else
	{
		printf("Direct GLX rendering context obtained\n");
	}

	glXMakeCurrent(mWindow->mDisplay, mWindow->mWindow, mWindow->mOglContext);
#endif
}

/* Zpracuj zpravy na okne */
void winProcessMessage(CWindow *mWindow, CInput *mInput, bool *mApplicationState)
{
#ifdef _WIN32
  if(PeekMessage(&mWindow->mMsg, NULL, 0, 0, PM_REMOVE))
  {
    if((mWindow->mMsg.message == WM_QUIT) || (mInput->mKeys[VK_ESCAPE] != 0))
    {
      *mApplicationState = 1;
    }
    else
    {
      TranslateMessage(&mWindow->mMsg);
      DispatchMessage(&mWindow->mMsg);
    }
  }
#else
  if(mInput->mMouseWheelCurrent != mInput->mMouseWheelLast)
  {
    mInput->mMouseWheelLast = mInput->mMouseWheelCurrent;
  }
  
  KeySym mKey;
  
  XWindowAttributes mWindowAttributes;
  
  while(XPending(mWindow->mDisplay) > 0)
  {
    XNextEvent(mWindow->mDisplay, &mWindow->mEvent);
    
    switch(mWindow->mEvent.type)
    {
      case Expose:
				XGetWindowAttributes(mWindow->mDisplay, mWindow->mWindow, &mWindowAttributes);
				mWindow->mWidth = mWindowAttributes.width;
				mWindow->mHeight = mWindowAttributes.height;
        break;
        
      case ClientMessage:
        if(*XGetAtomName(mWindow->mDisplay, mWindow->mEvent.xclient.message_type) == *"WM_PROTOCOLS")
        {
          *mApplicationState = true;
        }
        break;
	
      case KeyPress:
				mKey = XLookupKeysym(&mWindow->mEvent.xkey, 0);
				mInput->mKeys[(int)mKey] = true;
	
				// Escape ukoncuje
				if((int)mKey == XK_Escape)
				{
					*mApplicationState = true;
				}
	
				// For debugging reasons...
				//printf("Pressed %d\n", (int)mKey);
				break;
	
      case KeyRelease:
				if(mWindow->mEvent.type == KeyRelease && XEventsQueued(mWindow->mDisplay, QueuedAfterReading))
				{
					XEvent mNextEvent;
					XPeekEvent(mWindow->mDisplay, &mNextEvent);

					if(mNextEvent.type == KeyPress && 
						 mNextEvent.xkey.time == mWindow->mEvent.xkey.time &&
						 mNextEvent.xkey.keycode == mWindow->mEvent.xkey.keycode)
					{
					}
					else
					{
						mKey = XLookupKeysym(&mWindow->mEvent.xkey, 0);
						mInput->mKeys[(int)mKey] = false;
					
						// For debugging reasons...
						//printf("Released %d\n", (int)key);
					}
				}
				else
				{
					mKey = XLookupKeysym(&mWindow->mEvent.xkey, 0);
					mInput->mKeys[(int)mKey] = false;
			
					// For debugging reasons
					//printf("Released %d\n", (int)key);
				}
				break;
	
      case ButtonPress:
				switch(mWindow->mEvent.xbutton.button)
				{
					case 1:
						mInput->mMouseLeft = 1;
						break;
					case 2:
						mInput->mMouseMiddle = 1;
						break;
					case 3:
						mInput->mMouseRight = 1;
						break;
					case 4:
						mInput->mMouseWheelCurrent++;
						break;
					case 5:
						mInput->mMouseWheelCurrent--;
						break;
				}
				break;
	
      case ButtonRelease:
				switch(mWindow->mEvent.xbutton.button)
				{
					case 1:
						mInput->mMouseLeft = 0;
						break;
					case 2:
						mInput->mMouseMiddle = 0;
						break;
					case 3:
						mInput->mMouseRight = 0;
						break;
				}
				break;
        
      default:
        break;
    }
    
    mInput->mMousePos[0] = mWindow->mEvent.xmotion.x;
    mInput->mMousePos[1] = mWindow->mEvent.xmotion.y;
  }
#endif
}

/* Uvolni pamet okna a znic jej */
void winReleaseWindow(CWindow *mWindow)
{
#ifdef _WIN32
  if(mWindow->mFullscreen)									
	{
		ChangeDisplaySettings(NULL, 0);
		ShowCursor(true);
	}

  wglMakeCurrent(NULL, NULL);
  wglDeleteContext(mWindow->mOglContext);
  mWindow->mOglContext = NULL;

  ReleaseDC(mWindow->mWindow, mWindow->mDeviceContext);
  mWindow->mDeviceContext = NULL;

  DestroyWindow(mWindow->mWindow);
  mWindow->mWindow = NULL;

  UnregisterClass("SkyeCuillin", mWindow->mInstance);
  mWindow->mInstance = NULL;
#else
	glXMakeCurrent(mWindow->mDisplay, 0, 0);
	glXDestroyContext(mWindow->mDisplay, mWindow->mOglContext);

	XDestroyWindow(mWindow->mDisplay, mWindow->mWindow);
	XFreeColormap(mWindow->mDisplay, mWindow->mColorMap);
	XCloseDisplay(mWindow->mDisplay);
#endif
}
/////////////////////